/*
Autor: Alexander Savochkin, 2010-2013
New BSD License
*/

#include <algorithm>
#include <exception>
#include <utility>
#include <boost/none.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/log/formatters.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/attributes/constant.hpp>

#include "Config.h"
#include "Exception.h"
#include "Manager.h"
#include "CommandParser.h"
#include "CommandASTActions.h"
#include "FactoriesKeeper.h"
#include "IoServiceSource.h"

using std::remove_copy_if;
using std::not_equal_to;
using std::copy;
using std::map;
using std::min_element;
using std::exception;
using std::make_pair;

using boost::none;
using boost::lexical_cast;

const char* const CHANNEL_INPUT_ATTR = "input"; 
const char* const CHANNEL_OUTPUT_ATTR = "output"; 
const char* const CHANNEL_REGULATOR_ATTR = "regulator";	
const char* const CHANNEL_NEED_CAPTURE_ATTR = "need_capture";
const char* const CHANNEL_WATCH_CONNECTION_ATTR = "watch_connection";
const char* const CHANNEL_CONTROL_CONNECTION_ATTR = "control_connection";
const char* const CHANNEL_ADMIN_CONNECTION_ATTR = "admin_connection";
const char* const CHANNEL_TIME_INTERVAL_ATTR = "time_interval";

namespace SRCLF {

using ClientRequestParsing::CommandParser;	
using ClientRequestParsing::AST::CommandsVisitor;
using Config::Section;

	Manager::Manager() :
		client_id_sequence(0), timer(getCommonIOService())
	{
		namespace attributes = boost::log::attributes;
		//
		boost::shared_ptr< boost::log::attribute > attr(new attributes::constant< string >("Manager"));
	    logger.add_attribute("module", attr);
	}


	Manager::~Manager()
	{
		//Delete all dynamically created stuff (such as channels, inputs, outputs, e.t.c)
		//map<string, AccessInfo<Channel*> > channels;
		for(map<string, AccessInfo<Channel*> >::iterator ich = channels.begin(); ich != channels.end(); ++ich)
			delete ich->second.item;
	}

	void Manager::processCommand(ClientId id, const string& str_cmd,  string& response)
	{
		//TODO: Add clinet id as "scoped attribute"
		BOOST_LOG_SCOPED_LOGGER_TAG(logger, "client id", ClientId, id);

		BOOST_LOG_SEV(logger, normal) << "Incomming new command " << str_cmd;

		Command command;
		if(!CommandParser::parseCommand(str_cmd, command))
		{
			BOOST_LOG_SEV(logger, normal) << "command parse error";
			Response::onCommandParseError(response);
		}
		else
		{
			BOOST_LOG_SEV(logger, normal) << "command successfully parsed";
			processCommand(id, command, response);
		}
	}

	void Manager::processCommand(ClientId client_id, const Command& command,  string& response)
	{
		map<ClientId, ClientInfo>::const_iterator ic = client_id_2_info.find(client_id);
		assert(ic != client_id_2_info.end());
		CommandsVisitor channel_commands_visitor( this, &(ic->second), response );
		if (command.command)
			boost::apply_visitor( channel_commands_visitor, command.command.get() );
	}

	//
	bool Manager::checkControlOnChannel(const string& channel_name, const ClientInfo* client_info, string& result) const
	{
		const Channel *channel = getChannelByName(channel_name);
		//User should have "control" privelegies to perform this action
		if(!canControlChannel(channel_name, client_info))
		{
			Response::onAccessDenied(result); //Access denied
			return false;
		}
		bool can_control =  (!channel->needCaptureControl()) || (isCapturedControl(channel_name, client_info->getClientId()));
		if ( !can_control )
		{
			Response::onOtherClientCapturedControl(result);
			return false;
		}
		return true;
	}

	//
	bool Manager::checkAdminOnChannel(const string& channel_name, const ClientInfo* client_info, string& result) const
	{
		//User should have "admin" privelegies to perform this action	
		if(!canAdminChannel(channel_name, client_info))
		{
			Response::onAccessDenied(result); //Access denied
			return false;
		}
		return true;
	}

	//
	Channel* Manager::getChannelByName(const string& channel_name) const
	{
		map<string, AccessInfo<Channel*> >::const_iterator ich;
		if ( (ich = channels.find(channel_name)) != channels.end() ) 
			return ich->second.item;
		else
			return NULL;
	}

	void Manager::init( Configuration& config )
	{
		for(Configuration::Sections::const_iterator it_section = config.getSections().begin();
			it_section != config.getSections().end(); ++it_section ) 
		{
			if ( it_section->second.getType() != Section::channel )
				continue;

			IOperatedInput *input = NULL;
			IOperatedOutput *output = NULL;
			IRegulator *regulator = NULL;
			try {
				const Section& section = it_section->second;
		
				//Channel information
				string channel_name = section.getName(), 
					input_name = section.getScalarByKey(CHANNEL_INPUT_ATTR), 
					output_name = section.getScalarByKey(CHANNEL_OUTPUT_ATTR), 
					regulator_name = section.getScalarByKey(CHANNEL_REGULATOR_ATTR);	

				input = FactoriesKeeper::getInstance().newInputInstance(input_name, config);
				input->open();
				output = FactoriesKeeper::getInstance().newOutputInstance(output_name, config);
				output->open();
				regulator = FactoriesKeeper::getInstance().newRegulatorInstance(regulator_name, config);

				//Connections info
				string watch_connection, control_connection, admin_connection;

				watch_connection = section.getScalarByKey(CHANNEL_WATCH_CONNECTION_ATTR);
				control_connection = section.getScalarByKey(CHANNEL_CONTROL_CONNECTION_ATTR);
				admin_connection = section.getScalarByKey(CHANNEL_ADMIN_CONNECTION_ATTR);

				double control_interval = lexical_cast<double>(section.getScalarByKey(CHANNEL_TIME_INTERVAL_ATTR));
				double time_remains = 0.0; //TODO
				bool need_capture = lexical_cast<bool>(section.getScalarByKey(CHANNEL_NEED_CAPTURE_ATTR) );

				//Create object			
				Channel *channel = new Channel(input, output, regulator, 
					false, need_capture, control_interval, time_remains);
				ptime t = microsec_clock::universal_time();
				channel->setNextControlTime(t);

				//Access control structure
				AccessInfo<Channel*> access_info;
				access_info.item = channel;
				access_info.can_watch = watch_connection;
				access_info.can_control = control_connection;
				access_info.can_admin = admin_connection;

				registered_connections.insert(watch_connection);
				registered_connections.insert(control_connection);
				registered_connections.insert(admin_connection);

				channels[channel_name] = access_info;
			}
			catch(exception& e)
			{
				if (input != NULL)
					delete input;
				if (output != NULL)
					delete output;
				if (regulator != NULL)
					delete regulator;
				throw;
			}
		}

		//Start time handling events
		onTimerAction();
	}

	void Manager::notifyClientDisconnected(ClientId client_id)
	{
		for(map<string, ClientId>::iterator it = captured_channel.begin(); it != captured_channel.end(); ) {
			if (it->second == client_id)
				captured_channel.erase( it++ );
			else
				++it;
		}
		client_id_2_info.erase(client_id);
	}

	void Manager::notifyClientConnected( const ClientInfo& client_info )
	{
		client_id_2_info.insert( make_pair(client_info.getClientId(), client_info) );
	}

	ptime Manager::nextRegulateMoment() const
	{
		map<string, AccessInfo<Channel*> >::const_iterator it_begin = channels.begin();
		ptime nearest_future_regulate_moment = it_begin->second.item->getNextControlTime();
		for (map<string, AccessInfo<Channel*> >::const_iterator it = ++it_begin; it != channels.end(); ++it ) {
			const ptime& t = it->second.item->getNextControlTime();
			if ( t < nearest_future_regulate_moment)
				nearest_future_regulate_moment = t;
		}
		return nearest_future_regulate_moment;
	}

	bool Manager::isCapturedControl(const string& channel_name, ClientId client_id) const
	{
		return (captured_channel.count(channel_name) != 0);
	}

	bool Manager::canControlChannel(const string& channel_name, const ClientInfo* client_info) const
	{
		string connection_string = client_info->getConnectionString();
		map<string, AccessInfo<Channel*> >::const_iterator it = channels.find(channel_name);
		if ( it == channels.end())
			return false;
		if ( it->second.can_control == connection_string )
			return true;
		return false;
	}

	bool Manager::canAdminChannel(const string& channel_name, const ClientInfo* client_info) const
	{
		string connection_string = client_info->getConnectionString();
		map<string, AccessInfo<Channel*> >::const_iterator it = channels.find(channel_name);
		if ( it == channels.end())
			return false;
		if ( it->second.can_admin == connection_string )
			return true;
		return false;
	}

	bool Manager::captureControl(const string& channel_name, const ClientInfo* client_info)
	{
		BOOST_LOG_SEV(logger, notification) << "Trying to capture control";
		ClientId client_id = client_info->getClientId();
		map<string, ClientId >::const_iterator it = captured_channel.find(channel_name);
		if ( it == captured_channel.end() ) 
		{
			captured_channel[channel_name] = client_id;
			channels.find(channel_name)->second.owner_id = client_id;
			BOOST_LOG_SEV(logger, notification) << "Control captured succesfully";
			return true;
		}
		else if ( it->second == client_id )
		{
			BOOST_LOG_SEV(logger, notification) << "Control already captured";			
			return true;
		}

		BOOST_LOG_SEV(logger, notification) << "Control access denied";
		return false;
	}

	void Manager::releaseControl(const string& channel_name)
	{
		BOOST_LOG_SEV(logger, notification) << "Releasing control";
		captured_channel.erase( channel_name );
		channels.find(channel_name)->second.owner_id = none;
	}

	void Manager::onTimerAction()
	{

		ptime current_moment = microsec_clock::universal_time();

		for (map<string, AccessInfo<Channel*> >::const_iterator it = channels.begin(); it != channels.end(); ++it ) 
		{
			const ptime& t = it->second.item->getNextControlTime();
			if ( t < current_moment)
				//It's time to do some action on this channel
				it->second.item->update(current_moment);
		}

		ptime next_action_time = nextRegulateMoment();
		
		//Reschedule action
		timer.expires_at(next_action_time);
		timer.async_wait(boost::bind(&Manager::onTimerAction, this));

	}

} //namespace SRCLF
